package apple.core;

import org.apache.log4j.Logger;

public class MOS6502 {

   static Logger logger = Logger.getLogger(MOS6502.class);

   public static final short NMI_VECTOR = (short) 0xFFFA;

   public static final short RESET_VECTOR = (short) 0xFFFC;

   public static final short IRQ_VECTOR = (short) 0xFFFE;

   public static final byte ZERO_PAGE = 0x00;

   public static final byte STACK_PAGE = 0x01;

   public enum AddressingMode {
      IMPLIED, ACCUMULATOR, IMMEDIATE, ZERO_PAGE, ZERO_PAGE_X, ZERO_PAGE_Y, RELATIVE, ABSOLUTE, ABSOLUTE_X, ABSOLUTE_Y, INDIRECT, INDEXED_INDIRECT, INDIRECT_INDEXED
   };

   /*
    * Registers. These are declared protected so that they can be accessed by
    * JUnit tests.
    */
   protected short programCounter;

   protected byte stackPointer;

   protected byte indexRegisterX;

   protected byte indexRegisterY;

   protected byte accumulator;

   class ProcessorStatus {
      public boolean carryFlag;

      public boolean zeroFlag;

      public boolean interruptDisable;

      public boolean decimalMode;

      public boolean breakCommand;

      public boolean overflowFlag;

      public boolean negativeFlag;

      public String toString() {
         return "N=" + (negativeFlag ? 1 : 0) + " V=" + (overflowFlag ? 1 : 0)
               + " B=" + (breakCommand ? 1 : 0) + " D=" + (decimalMode ? 1 : 0)
               + " I=" + (interruptDisable ? 1 : 0) + " Z="
               + (zeroFlag ? 1 : 0) + " C=" + (carryFlag ? 1 : 0);
      }

      public void setNZ(byte result) {
         zeroFlag = result == 0;
         negativeFlag = (result & 0x80) == 0x80;
      }

      public byte getByte() {
         return (byte) ((carryFlag ? 0x1 : 0) | (zeroFlag ? 0x2 : 0)
               | (interruptDisable ? 0x4 : 0) | (decimalMode ? 0x8 : 0)
               | (breakCommand ? 0x10 : 0) | 0x20 | (overflowFlag ? 0x40 : 0) | (negativeFlag ? 0x80
               : 0));
      }

      public void setByte(byte flags) {
         carryFlag = (flags & 0x1) == 0x1;
         zeroFlag = (flags & 0x2) == 0x2;
         interruptDisable = (flags & 0x4) == 0x4;
         decimalMode = (flags & 0x8) == 0x8;
         breakCommand = (flags & 0x10) == 0x10;
         overflowFlag = (flags & 0x40) == 0x40;
         negativeFlag = (flags & 0x80) == 0x80;
      }
   }

   ProcessorStatus processorStatus = new ProcessorStatus();

   Memory memory;

   private long cycles = 0;

   private boolean halt = false;

   private boolean suspend = false;

   public MOS6502() {
      this.memory = new Memory();
   }

   public MOS6502(Memory memory) {
      this.memory = memory;
   }

   public void reset() {
      stackPointer = (byte) 0xFF;
      programCounter = memory.readWord(RESET_VECTOR);
   }

   public String toString() {
      return String.format("PC=%04X SP=%02X A=%02X X=%02X Y=%02X : %s",
            programCounter, stackPointer, accumulator, indexRegisterX,
            indexRegisterY, processorStatus);
   }

   /*
    * Stack Utilities. These are declared package-private so that they can be
    * accessed by JUnit tests.
    */

   protected short getStackAddress() {
      return Utilities.getWord(STACK_PAGE, stackPointer);
   }

   protected void pushByte(byte datum) {
      short address = getStackAddress();
      memory.write(address, datum);
      stackPointer--;
   }

   protected byte pullByte() {
      stackPointer++;
      short address = getStackAddress();
      return memory.read(address);
   }

   protected void pushWord(short word) {
      byte highByte = (byte) (word >> 8);
      byte lowByte = (byte) (word & 0xFF);
      pushByte(highByte);
      pushByte(lowByte);
   }

   protected short pullWord() {
      byte lowByte = pullByte();
      byte highByte = pullByte();
      return Utilities.getWord(highByte, lowByte);
   }

   /*
    * Program Execution Utilities
    */

   /**
    * Fetches the next byte from the program and increments the Program Counter.
    * 
    * @return the next byte from the program
    */
   byte fetchProgramByte() {
      return memory.read(programCounter++);
   }

   /**
    * Convenience method for fetching the next two bytes from the program as a
    * little-endian address.
    * 
    * @return the address represented by the two fetched bytes
    */
   short fetchProgramAddress() {
      byte lowByte = fetchProgramByte();
      byte highByte = fetchProgramByte();
      return Utilities.getWord(highByte, lowByte);
   }

   byte getOperand(AddressingMode mode) {
      short address;

      switch (mode) {

      case IMMEDIATE:
      case RELATIVE:
         return fetchProgramByte();

      case ZERO_PAGE:
      case ZERO_PAGE_X:
      case ZERO_PAGE_Y:
      case ABSOLUTE:
      case ABSOLUTE_X:
      case ABSOLUTE_Y:
      case INDEXED_INDIRECT:
      case INDIRECT_INDEXED:
         address = getOperandAddress(mode);
         return memory.read(address);

      default:
         String error = "Unhandled addressing mode: " + mode;
         assert false : error;
         logger.fatal(error);
         return 0;
      }
   }

   short getOperandAddress(AddressingMode mode) {

      byte datum;
      short address;
      short word;

      switch (mode) {

      case ZERO_PAGE:
         datum = fetchProgramByte();
         return Utilities.getWord(ZERO_PAGE, datum);

      case ZERO_PAGE_X:
         datum = fetchProgramByte();
         datum += indexRegisterX;
         return Utilities.getWord(ZERO_PAGE, datum);

      case ZERO_PAGE_Y:
         datum = fetchProgramByte();
         datum += indexRegisterY;
         return Utilities.getWord(ZERO_PAGE, datum);

      case ABSOLUTE:
         return fetchProgramAddress();

      case ABSOLUTE_X:
         address = fetchProgramAddress();
         return Utilities.getAddress(address, indexRegisterX);

      case ABSOLUTE_Y:
         address = fetchProgramAddress();
         return Utilities.getAddress(address, indexRegisterY);

      case INDIRECT:
         address = fetchProgramAddress();

         byte lowByte = memory.read(address);

         // bug in 6502: offset wraps to same page
         byte page = (byte) (address >> 8);
         byte offset = (byte) (address & 0xFF);
         offset++;
         address = Utilities.getWord(page, offset);
         byte highByte = memory.read(address);

         return Utilities.getWord(highByte, lowByte);

      case INDEXED_INDIRECT:
         datum = fetchProgramByte();
         datum += indexRegisterX;

         address = Utilities.getWord(ZERO_PAGE, datum);
         lowByte = memory.read(address);

         // both bytes must be on zero page (possible wrap)
         datum++;
         address = Utilities.getWord(ZERO_PAGE, datum);
         highByte = memory.read(address);

         return Utilities.getWord(highByte, lowByte);

      case INDIRECT_INDEXED:
         datum = fetchProgramByte();
         address = Utilities.getAddress(ZERO_PAGE, datum);
         word = memory.readWord(address);
         return Utilities.getAddress(word, indexRegisterY);

      default:
         String error = "Unhandled addressing mode: " + mode;
         assert false : error;
         logger.fatal(error);
         return 0;
      }
   }

   /*
    * Instructions. These are declared package-private so that they can be
    * accessed by JUnit tests.
    */

   protected void ADC(AddressingMode mode) {
      if (processorStatus.decimalMode) {
         logger.error("Binary-coded decimal not yet implemented.");
         System.exit(-1);
      }
      byte operand = getOperand(mode);
      // add as unsigned numbers
      int unsignedResult = (accumulator & 0xFF) + (operand & 0xFF)
            + (processorStatus.carryFlag ? 1 : 0);
      // add as signed numbers
      int signedResult = accumulator + operand
            + (processorStatus.carryFlag ? 1 : 0);
      // convert back to signed number
      accumulator = (byte) signedResult;
      processorStatus.setNZ(accumulator);
      // flags will indicate sign of result
      processorStatus.carryFlag = unsignedResult > 0xFF;
      processorStatus.overflowFlag = signedResult > (byte) 0x7F
            || signedResult < (byte) 0x80;
   }

   protected void AND(AddressingMode mode) {
      byte operand = getOperand(mode);
      accumulator &= operand;
      processorStatus.setNZ(accumulator);
   }

   protected void ASL(AddressingMode mode) {
      switch (mode) {
      case ACCUMULATOR:
         processorStatus.carryFlag = (accumulator & 0x80) == 0x80;
         accumulator <<= 1;
         processorStatus.setNZ(accumulator);
         break;
      default:
         short address = getOperandAddress(mode);
         byte datum = memory.read(address);
         processorStatus.carryFlag = (datum & 0x80) == 0x80;
         datum <<= 1;
         processorStatus.setNZ(datum);
         memory.write(address, datum);
         break;
      }
   }

   protected void BCC(AddressingMode mode) {
      int cycles = 2;
      byte operand = getOperand(mode);
      if (!processorStatus.carryFlag) {
         programCounter += operand;
         cycles += 1;
      }
      this.cycles += cycles;
   }

   protected void BCS(AddressingMode mode) {
      int cycles = 2;
      byte operand = getOperand(mode);
      if (processorStatus.carryFlag) {
         programCounter += operand;
         cycles += 1;
      }
      this.cycles += cycles;
   }

   protected void BEQ(AddressingMode mode) {
      int cycles = 2;
      byte operand = getOperand(mode);
      if (processorStatus.zeroFlag) {
         programCounter += operand;
         cycles += 1;
      }
      this.cycles += cycles;
   }

   protected void BIT(AddressingMode mode) {
      byte operand = getOperand(mode);
      processorStatus.negativeFlag = (operand & 0x80) == 0x80;
      processorStatus.overflowFlag = (operand & 0x40) == 0x40;
      processorStatus.zeroFlag = (accumulator & operand) == 0;
   }

   protected void BMI(AddressingMode mode) {
      int cycles = 2;
      byte operand = getOperand(mode);
      if (processorStatus.negativeFlag) {
         programCounter += operand;
         cycles += 1;
      }
      this.cycles += cycles;
   }

   protected void BNE(AddressingMode mode) {
      int cycles = 2;
      byte operand = getOperand(mode);
      if (!processorStatus.zeroFlag) {
         programCounter += operand;
         cycles += 1;
      }
      this.cycles += cycles;
   }

   protected void BPL(AddressingMode mode) {
      int cycles = 2;
      byte operand = getOperand(mode);
      if (!processorStatus.negativeFlag) {
         programCounter += operand;
         cycles += 1;
      }
      this.cycles += cycles;
   }

   protected void BRK() {
      pushWord((short) (programCounter + 1));
      pushByte(processorStatus.getByte());
      processorStatus.breakCommand = true;
      programCounter = memory.readWord(IRQ_VECTOR);
   }

   protected void BVC(AddressingMode mode) {
      int cycles = 2;
      byte operand = getOperand(mode);
      if (!processorStatus.overflowFlag) {
         programCounter += operand;
         cycles += 1;
      }
      this.cycles += cycles;
   }

   protected void BVS(AddressingMode mode) {
      int cycles = 2;
      byte operand = getOperand(mode);
      if (processorStatus.overflowFlag) {
         programCounter += operand;
         cycles += 1;
      }
      this.cycles += cycles;
   }

   protected void CLC() {
      processorStatus.carryFlag = false;
      cycles += 2;
   }

   protected void CLD() {
      processorStatus.decimalMode = false;
      cycles += 2;
   }

   protected void CLI() {
      processorStatus.interruptDisable = false;
      cycles += 2;
   }

   protected void CLV() {
      processorStatus.overflowFlag = false;
      cycles += 2;
   }

   protected void CMP(AddressingMode mode) {
      byte datum = getOperand(mode);
      int unsignedResult = (accumulator & 0xFF) - (datum & 0xFF);
      byte signedResult = (byte) (accumulator - datum);
      processorStatus.carryFlag = unsignedResult >= 0;
      processorStatus.setNZ(signedResult);
   }

   protected void CPX(AddressingMode mode) {
      byte datum = getOperand(mode);
      int unsignedResult = (indexRegisterX & 0xFF) - (datum & 0xFF);
      byte signedResult = (byte) (indexRegisterX - datum);
      processorStatus.carryFlag = unsignedResult >= 0;
      processorStatus.setNZ(signedResult);
   }

   protected void CPY(AddressingMode mode) {
      byte datum = getOperand(mode);
      int unsignedResult = (indexRegisterY & 0xFF) - (datum & 0xFF);
      byte signedResult = (byte) (indexRegisterY - datum);
      processorStatus.carryFlag = unsignedResult >= 0;
      processorStatus.setNZ(signedResult);
   }

   protected void DEC(AddressingMode mode) {
      short address = getOperandAddress(mode);
      byte datum = memory.read(address);
      datum--;
      processorStatus.setNZ(datum);
      memory.write(address, datum);
   }

   protected void DEX() {
      indexRegisterX--;
      processorStatus.setNZ(indexRegisterX);
      cycles += 2;
   }

   protected void DEY() {
      indexRegisterY--;
      processorStatus.setNZ(indexRegisterY);
      cycles += 2;
   }

   protected void EOR(AddressingMode mode) {
      byte operand = getOperand(mode);
      accumulator ^= operand;
      processorStatus.setNZ(accumulator);
   }

   protected void INC(AddressingMode mode) {
      short address = getOperandAddress(mode);
      byte datum = memory.read(address);
      datum++;
      processorStatus.setNZ(datum);
      memory.write(address, datum);
   }

   protected void INX() {
      indexRegisterX++;
      processorStatus.setNZ(indexRegisterX);
      cycles += 2;
   }

   protected void INY() {
      indexRegisterY++;
      processorStatus.setNZ(indexRegisterY);
      cycles += 2;
   }

   protected void JMP(AddressingMode mode) {
      short address = getOperandAddress(mode);
      programCounter = address;
   }

   protected void JSR(AddressingMode mode) {
      short address = getOperandAddress(mode);
      pushWord((short) (programCounter - 1));
      programCounter = address;
      cycles += 6;
   }

   protected void LDA(AddressingMode mode) {
      accumulator = getOperand(mode);
      processorStatus.setNZ(accumulator);
   }

   protected void LDX(AddressingMode mode) {
      indexRegisterX = getOperand(mode);
      processorStatus.setNZ(indexRegisterX);
   }

   protected void LDY(AddressingMode mode) {
      indexRegisterY = getOperand(mode);
      processorStatus.setNZ(indexRegisterY);
   }

   protected void LSR(AddressingMode mode) {
      switch (mode) {
      case ACCUMULATOR:
         processorStatus.carryFlag = (accumulator & 0x1) == 0x1;
         // the following does not work -- some consider it a Java bug
         // accumulator >>>= 1;
         accumulator = (byte) ((accumulator & 0xFF) >> 1);
         processorStatus.setNZ(accumulator);
         break;
      default:
         short address = getOperandAddress(mode);
         byte datum = memory.read(address);
         processorStatus.carryFlag = (datum & 0x1) == 0x1;
         // the following does not work -- some consider it a Java bug
         // datum >>>= 1;
         datum = (byte) ((datum & 0xFF) >> 1);
         processorStatus.setNZ(datum);
         memory.write(address, datum);
         break;
      }
   }

   protected void NOP() {
   }

   protected void ORA(AddressingMode mode) {
      byte operand = getOperand(mode);
      accumulator |= operand;
      processorStatus.setNZ(accumulator);
   }

   protected void PHA() {
      pushByte(accumulator);
      cycles += 3;
   }

   protected void PHP() {
      pushByte(processorStatus.getByte());
      cycles += 3;
   }

   protected void PLA() {
      accumulator = pullByte();
      processorStatus.setNZ(accumulator);
      cycles += 4;
   }

   protected void PLP() {
      processorStatus.setByte(pullByte());
      cycles += 4;
   }

   protected void ROL(AddressingMode mode) {
      boolean newCarry;
      switch (mode) {
      case ACCUMULATOR:
         newCarry = (accumulator & 0x80) == 0x80;
         accumulator <<= 1;
         if (processorStatus.carryFlag) {
            accumulator |= 0x1;
         }
         processorStatus.carryFlag = newCarry;
         processorStatus.setNZ(accumulator);
         break;
      default:
         short address = getOperandAddress(mode);
         byte datum = memory.read(address);
         newCarry = (datum & 0x80) == 0x80;
         datum <<= 1;
         if (processorStatus.carryFlag) {
            datum |= 0x1;
         }
         processorStatus.carryFlag = newCarry;
         processorStatus.setNZ(datum);
         memory.write(address, datum);
         break;
      }
   }

   protected void ROR(AddressingMode mode) {
      boolean newCarry;
      switch (mode) {
      case ACCUMULATOR:
         newCarry = (accumulator & 0x1) == 0x1;
         // the following does not work -- some consider it a Java bug
         // accumulator >>>= 1;
         accumulator = (byte) ((accumulator & 0xFF) >> 1);
         if (processorStatus.carryFlag) {
            accumulator |= 0x80;
         }
         processorStatus.carryFlag = newCarry;
         processorStatus.setNZ(accumulator);
         break;
      default:
         short address = getOperandAddress(mode);
         byte datum = memory.read(address);
         newCarry = (datum & 0x1) == 0x1;
         // the following does not work -- some consider it a Java bug
         // datum >>>= 1;
         datum = (byte) ((datum & 0xFF) >> 1);
         if (processorStatus.carryFlag) {
            datum |= 0x80;
         }
         processorStatus.carryFlag = newCarry;
         processorStatus.setNZ(datum);
         memory.write(address, datum);
         break;
      }
   }

   protected void RTI() {
      processorStatus.setByte(pullByte());
      programCounter = pullWord();
      cycles += 6;
   }

   protected void RTS() {
      programCounter = pullWord();
      programCounter++;
      cycles += 6;
   }

   protected void SBC(AddressingMode mode) {
      if (processorStatus.decimalMode) {
         logger.error("Binary-coded decimal not yet implemented.");
         System.exit(-1);
      }
      byte operand = getOperand(mode);
      // add as unsigned numbers
      int unsignedResult = (accumulator & 0xFF) - (operand & 0xFF)
            - (processorStatus.carryFlag ? 0 : 1);
      // add as signed numbers
      int signedResult = accumulator - operand
            - (processorStatus.carryFlag ? 0 : 1);
      // convert back to signed number
      accumulator = (byte) signedResult;
      processorStatus.setNZ(accumulator);
      // flags will indicate sign of result
      processorStatus.carryFlag = unsignedResult >= 0x0;
      processorStatus.overflowFlag = signedResult > (byte) 0x7F
            || signedResult < (byte) 0x80;
   }

   protected void SEC() {
      processorStatus.carryFlag = true;
      cycles += 2;
   }

   protected void SED() {
      logger.error("Binary-coded decimal not yet implemented.");
      System.exit(-1);
      processorStatus.decimalMode = true;
      cycles += 2;
   }

   protected void SEI() {
      processorStatus.interruptDisable = true;
      cycles += 2;
   }

   protected void STA(AddressingMode mode) {
      short address = getOperandAddress(mode);
      memory.write(address, accumulator);
   }

   protected void STX(AddressingMode mode) {
      short address = getOperandAddress(mode);
      memory.write(address, indexRegisterX);
   }

   protected void STY(AddressingMode mode) {
      short address = getOperandAddress(mode);
      memory.write(address, indexRegisterY);
   }

   protected void TAX() {
      indexRegisterX = accumulator;
      processorStatus.setNZ(indexRegisterX);
      cycles += 2;
   }

   protected void TAY() {
      indexRegisterY = accumulator;
      processorStatus.setNZ(indexRegisterY);
      cycles += 2;
   }

   protected void TSX() {
      indexRegisterX = stackPointer;
      processorStatus.setNZ(indexRegisterX);
      cycles += 2;
   }

   protected void TXA() {
      accumulator = indexRegisterX;
      processorStatus.setNZ(accumulator);
      cycles += 2;
   }

   protected void TXS() {
      stackPointer = indexRegisterX;
      cycles += 2;
   }

   protected void TYA() {
      accumulator = indexRegisterY;
      processorStatus.setNZ(accumulator);
      cycles += 2;
   }

   /*
    * Main Loop
    */

   public void run() {
      while (!halt) {
         if (suspend) {
            synchronized (this) {
               while (suspend) {
                  try {
                     this.wait();
                  } catch (InterruptedException e) {
                     logger.debug(e);
                  }
               }
            }
         }
         processInstruction();
      }
   }

   /**
    * Signals the processor to halt. This method does not block.
    */
   public synchronized void halt() {
      if (suspend) {
         resume();
      }
      halt = true;
   }

   public synchronized void suspend() {
      suspend = true;
   }

   public synchronized void resume() {
      suspend = false;
      this.notify();
   }

   protected void processInstruction() {
      byte opCode = fetchProgramByte();
      switch (opCode) {

      case (byte) 0x00:
         BRK();
         break;

      case (byte) 0x01:
         ORA(AddressingMode.INDEXED_INDIRECT);
         break;

      case (byte) 0x05:
         ORA(AddressingMode.ZERO_PAGE);
         break;

      case (byte) 0x06:
         ASL(AddressingMode.ZERO_PAGE);
         break;

      case (byte) 0x08:
         PHP();
         break;

      case (byte) 0x09:
         ORA(AddressingMode.IMMEDIATE);
         break;

      case (byte) 0x0A:
         ASL(AddressingMode.ACCUMULATOR);
         break;

      case (byte) 0x0D:
         ORA(AddressingMode.ABSOLUTE);
         break;

      case (byte) 0x0E:
         ASL(AddressingMode.ABSOLUTE);
         break;

      case (byte) 0x10:
         BPL(AddressingMode.RELATIVE);
         break;

      case (byte) 0x11:
         ORA(AddressingMode.INDIRECT_INDEXED);
         break;

      case (byte) 0x15:
         ORA(AddressingMode.ZERO_PAGE_X);
         break;

      case (byte) 0x16:
         ASL(AddressingMode.ZERO_PAGE_X);
         break;

      case (byte) 0x18:
         CLC();
         break;

      case (byte) 0x19:
         ORA(AddressingMode.ABSOLUTE_Y);
         break;

      case (byte) 0x1D:
         ORA(AddressingMode.ABSOLUTE_X);
         break;

      case (byte) 0x1E:
         ASL(AddressingMode.ABSOLUTE_X);
         break;

      case (byte) 0x20:
         JSR(AddressingMode.ABSOLUTE);
         break;

      case (byte) 0x21:
         AND(AddressingMode.INDEXED_INDIRECT);
         break;

      case (byte) 0x24:
         BIT(AddressingMode.ZERO_PAGE);
         break;

      case (byte) 0x25:
         AND(AddressingMode.ZERO_PAGE);
         break;

      case (byte) 0x26:
         ROL(AddressingMode.ZERO_PAGE);
         break;

      case (byte) 0x28:
         PLP();
         break;

      case (byte) 0x29:
         AND(AddressingMode.IMMEDIATE);
         break;

      case (byte) 0x2A:
         ROL(AddressingMode.ACCUMULATOR);
         break;

      case (byte) 0x2C:
         BIT(AddressingMode.ABSOLUTE);
         break;

      case (byte) 0x2D:
         AND(AddressingMode.ABSOLUTE);
         break;

      case (byte) 0x2E:
         ROL(AddressingMode.ABSOLUTE);
         break;

      case (byte) 0x30:
         BMI(AddressingMode.RELATIVE);
         break;

      case (byte) 0x31:
         AND(AddressingMode.INDIRECT_INDEXED);
         break;

      case (byte) 0x35:
         AND(AddressingMode.ZERO_PAGE_X);
         break;

      case (byte) 0x36:
         ROL(AddressingMode.ZERO_PAGE_X);
         break;

      case (byte) 0x38:
         SEC();
         break;

      case (byte) 0x39:
         AND(AddressingMode.ABSOLUTE_Y);
         break;

      case (byte) 0x3D:
         AND(AddressingMode.ABSOLUTE_X);
         break;

      case (byte) 0x3E:
         ROL(AddressingMode.ABSOLUTE_X);
         break;

      case (byte) 0x40:
         RTI();
         break;

      case (byte) 0x41:
         EOR(AddressingMode.INDEXED_INDIRECT);
         break;

      case (byte) 0x45:
         EOR(AddressingMode.ZERO_PAGE);
         break;

      case (byte) 0x46:
         LSR(AddressingMode.ZERO_PAGE);
         break;

      case (byte) 0x48:
         PHA();
         break;

      case (byte) 0x49:
         EOR(AddressingMode.IMMEDIATE);
         break;

      case (byte) 0x4A:
         LSR(AddressingMode.ACCUMULATOR);
         break;

      case (byte) 0x4C:
         JMP(AddressingMode.ABSOLUTE);
         break;

      case (byte) 0x4D:
         EOR(AddressingMode.ABSOLUTE);
         break;

      case (byte) 0x4E:
         LSR(AddressingMode.ABSOLUTE);
         break;

      case (byte) 0x50:
         BVC(AddressingMode.RELATIVE);
         break;

      case (byte) 0x51:
         EOR(AddressingMode.INDIRECT_INDEXED);
         break;

      case (byte) 0x55:
         EOR(AddressingMode.ZERO_PAGE_X);
         break;

      case (byte) 0x56:
         LSR(AddressingMode.ZERO_PAGE_X);
         break;

      case (byte) 0x58:
         CLI();
         break;

      case (byte) 0x59:
         EOR(AddressingMode.ABSOLUTE_Y);
         break;

      case (byte) 0x5D:
         EOR(AddressingMode.ABSOLUTE_X);
         break;

      case (byte) 0x5E:
         LSR(AddressingMode.ABSOLUTE_X);
         break;

      case (byte) 0x60:
         RTS();
         break;

      case (byte) 0x61:
         ADC(AddressingMode.INDEXED_INDIRECT);
         break;

      case (byte) 0x65:
         ADC(AddressingMode.ZERO_PAGE);
         break;

      case (byte) 0x66:
         ROR(AddressingMode.ZERO_PAGE);
         break;

      case (byte) 0x68:
         PLA();
         break;

      case (byte) 0x69:
         ADC(AddressingMode.IMMEDIATE);
         break;

      case (byte) 0x6A:
         ROR(AddressingMode.ACCUMULATOR);
         break;

      case (byte) 0x6C:
         JMP(AddressingMode.INDIRECT);
         break;

      case (byte) 0x6D:
         ADC(AddressingMode.ABSOLUTE);
         break;

      case (byte) 0x6E:
         ROR(AddressingMode.ABSOLUTE);
         break;

      case (byte) 0x70:
         BVS(AddressingMode.RELATIVE);
         break;

      case (byte) 0x71:
         ADC(AddressingMode.INDIRECT_INDEXED);
         break;

      case (byte) 0x75:
         ADC(AddressingMode.ZERO_PAGE_X);
         break;

      case (byte) 0x76:
         ROR(AddressingMode.ZERO_PAGE_X);
         break;

      case (byte) 0x78:
         SEI();
         break;

      case (byte) 0x79:
         ADC(AddressingMode.ABSOLUTE_Y);
         break;

      case (byte) 0x7D:
         ADC(AddressingMode.ABSOLUTE_X);
         break;

      case (byte) 0x7E:
         ROR(AddressingMode.ABSOLUTE_X);
         break;

      case (byte) 0x81:
         STA(AddressingMode.INDEXED_INDIRECT);
         break;

      case (byte) 0x84:
         STY(AddressingMode.ZERO_PAGE);
         break;

      case (byte) 0x85:
         STA(AddressingMode.ZERO_PAGE);
         break;

      case (byte) 0x86:
         STX(AddressingMode.ZERO_PAGE);
         break;

      case (byte) 0x88:
         DEY();
         break;

      case (byte) 0x8A:
         TXA();
         break;

      case (byte) 0x8C:
         STY(AddressingMode.ABSOLUTE);
         break;

      case (byte) 0x8D:
         STA(AddressingMode.ABSOLUTE);
         break;

      case (byte) 0x8E:
         STX(AddressingMode.ABSOLUTE);
         break;

      case (byte) 0x90:
         BCC(AddressingMode.RELATIVE);
         break;

      case (byte) 0x91:
         STA(AddressingMode.INDIRECT_INDEXED);
         break;

      case (byte) 0x94:
         STY(AddressingMode.ZERO_PAGE_X);
         break;

      case (byte) 0x95:
         STA(AddressingMode.ZERO_PAGE_X);
         break;

      case (byte) 0x96:
         STX(AddressingMode.ZERO_PAGE_Y);
         break;

      case (byte) 0x98:
         TYA();
         break;

      case (byte) 0x99:
         STA(AddressingMode.ABSOLUTE_Y);
         break;

      case (byte) 0x9A:
         TXS();
         break;

      case (byte) 0x9D:
         STA(AddressingMode.ABSOLUTE_X);
         break;

      case (byte) 0xA0:
         LDY(AddressingMode.IMMEDIATE);
         break;

      case (byte) 0xA1:
         LDA(AddressingMode.INDEXED_INDIRECT);
         break;

      case (byte) 0xA2:
         LDX(AddressingMode.IMMEDIATE);
         break;

      case (byte) 0xA4:
         LDY(AddressingMode.ZERO_PAGE);
         break;

      case (byte) 0xA5:
         LDA(AddressingMode.ZERO_PAGE);
         break;

      case (byte) 0xA6:
         LDX(AddressingMode.ZERO_PAGE);
         break;

      case (byte) 0xA8:
         TAY();
         break;

      case (byte) 0xA9:
         LDA(AddressingMode.IMMEDIATE);
         break;

      case (byte) 0xAA:
         TAX();
         break;

      case (byte) 0xAC:
         LDY(AddressingMode.ABSOLUTE);
         break;

      case (byte) 0xAD:
         LDA(AddressingMode.ABSOLUTE);
         break;

      case (byte) 0xAE:
         LDX(AddressingMode.ABSOLUTE);
         break;

      case (byte) 0xB0:
         BCS(AddressingMode.RELATIVE);
         break;

      case (byte) 0xB1:
         LDA(AddressingMode.INDIRECT_INDEXED);
         break;

      case (byte) 0xB4:
         LDY(AddressingMode.ZERO_PAGE_X);
         break;

      case (byte) 0xB5:
         LDA(AddressingMode.ZERO_PAGE_X);
         break;

      case (byte) 0xB6:
         LDX(AddressingMode.ZERO_PAGE_Y);
         break;

      case (byte) 0xB8:
         CLV();
         break;

      case (byte) 0xB9:
         LDA(AddressingMode.ABSOLUTE_Y);
         break;

      case (byte) 0xBA:
         TSX();
         break;

      case (byte) 0xBC:
         LDY(AddressingMode.ABSOLUTE_X);
         break;

      case (byte) 0xBD:
         LDA(AddressingMode.ABSOLUTE_X);
         break;

      case (byte) 0xBE:
         LDX(AddressingMode.ABSOLUTE_Y);
         break;

      case (byte) 0xC0:
         CPY(AddressingMode.IMMEDIATE);
         break;

      case (byte) 0xC1:
         CMP(AddressingMode.INDEXED_INDIRECT);
         break;

      case (byte) 0xC4:
         CPY(AddressingMode.ZERO_PAGE);
         break;

      case (byte) 0xC5:
         CMP(AddressingMode.ZERO_PAGE);
         break;

      case (byte) 0xC6:
         DEC(AddressingMode.ZERO_PAGE);
         break;

      case (byte) 0xC8:
         INY();
         break;

      case (byte) 0xC9:
         CMP(AddressingMode.IMMEDIATE);
         break;

      case (byte) 0xCA:
         DEX();
         break;

      case (byte) 0xCC:
         CPY(AddressingMode.ABSOLUTE);
         break;

      case (byte) 0xCD:
         CMP(AddressingMode.ABSOLUTE);
         break;

      case (byte) 0xCE:
         DEC(AddressingMode.ABSOLUTE);
         break;

      case (byte) 0xD0:
         BNE(AddressingMode.RELATIVE);
         break;

      case (byte) 0xD1:
         CMP(AddressingMode.INDIRECT_INDEXED);
         break;

      case (byte) 0xD5:
         CMP(AddressingMode.ZERO_PAGE_X);
         break;

      case (byte) 0xD6:
         DEC(AddressingMode.ZERO_PAGE_X);
         break;

      case (byte) 0xD8:
         CLD();
         break;

      case (byte) 0xD9:
         CMP(AddressingMode.ABSOLUTE_Y);
         break;

      case (byte) 0xDD:
         CMP(AddressingMode.ABSOLUTE_X);
         break;

      case (byte) 0xDE:
         DEC(AddressingMode.ABSOLUTE_X);
         break;

      case (byte) 0xE0:
         CPX(AddressingMode.IMMEDIATE);
         break;

      case (byte) 0xE1:
         SBC(AddressingMode.INDEXED_INDIRECT);
         break;

      case (byte) 0xE4:
         CPX(AddressingMode.ZERO_PAGE);
         break;

      case (byte) 0xE5:
         SBC(AddressingMode.ZERO_PAGE);
         break;

      case (byte) 0xE6:
         INC(AddressingMode.ZERO_PAGE);
         break;

      case (byte) 0xE8:
         INX();
         break;

      case (byte) 0xE9:
         SBC(AddressingMode.IMMEDIATE);
         break;

      case (byte) 0xEA:
         NOP();
         break;

      case (byte) 0xEC:
         CPX(AddressingMode.ABSOLUTE);
         break;

      case (byte) 0xED:
         SBC(AddressingMode.ABSOLUTE);
         break;

      case (byte) 0xEE:
         INC(AddressingMode.ABSOLUTE);
         break;

      case (byte) 0xF0:
         BEQ(AddressingMode.RELATIVE);
         break;

      case (byte) 0xF1:
         SBC(AddressingMode.INDIRECT_INDEXED);
         break;

      case (byte) 0xF5:
         SBC(AddressingMode.ZERO_PAGE_X);
         break;

      case (byte) 0xF6:
         INC(AddressingMode.ZERO_PAGE_X);
         break;

      case (byte) 0xF8:
         SED();
         break;

      case (byte) 0xF9:
         SBC(AddressingMode.ABSOLUTE_Y);
         break;

      case (byte) 0xFD:
         SBC(AddressingMode.ABSOLUTE_X);
         break;

      case (byte) 0xFE:
         INC(AddressingMode.ABSOLUTE_X);
         break;

      default:
         String error = String.format("Illegal Instruction: %02X", opCode);
         assert false : error;
         logger.fatal(error);
      }
   }
}
